/*
 *
 *  This file is part of Cfu
 *
 *  Cfu is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  
 *  Cfu is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with Cfu. If not, see <http://www.gnu.org/licenses/>.
 *
 *  connector.cc - william le ferrand (william@corefarm.com) 29/04/2010 15:30
 *
 */


// STL
#include <iostream>
#include <istream>
#include <sstream>
#include <fstream>
#include <string>

// BOOST
#include <boost/asio.hpp>
#include <boost/iostreams/filtering_streambuf.hpp>
#include <boost/iostreams/copy.hpp>
#include <boost/iostreams/filter/gzip.hpp>
#include <boost/filesystem.hpp>

// CFU
#include <sha2.h>
#include <errors.hpp>
#include <json.hpp>

using boost::asio::ip::tcp;


std::string escape(std::string &str) {
  
  std::ostringstream str_e_s;
  
  std::string::iterator it;
  for ( it=str.begin() ; it < str.end(); it++ )
    {
      
      if ((*it == 0x2D) || (*it == 0x2E) || // Hyphen-Minus, Full Stop
	  ((0x30 <= *it) && (*it <= 0x39)) || // Digits [0-9]
	  ((0x41 <= *it) && (*it <= 0x5A)) || // Uppercase [A-Z]
	  ((0x61 <= *it) && (*it <= 0x7A)))  // Lowercase [a-z]
	str_e_s << *it;
      else
	{
	  char buffer[5];
	  sprintf(buffer, "%%%02X", *it);
	  str_e_s << buffer ;
	}
    }
  //   } else { printf("%%%02X", c); }
  
  return ( str_e_s.str ()) ;
}; 

std::string perform (std::string &server, boost::asio::streambuf &request) {
  boost::asio::io_service io_service;
  
  // Try each endpoint until we successfully establish a connection.
  tcp::socket socket(io_service);
  boost::system::error_code error = boost::asio::error::host_not_found;
 
  // Get a list of endpoints corresponding to the server name.
  tcp::resolver resolver(io_service);
  tcp::resolver::query query(server, "http");
  tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
  tcp::resolver::iterator end;
  
  while (error && endpoint_iterator != end)
    {
      socket.close();
	  socket.connect(*endpoint_iterator++, error);
    }
  if (error)
    throw boost::system::system_error(error);
  
  /*
    tcp::endpoint endpoint(boost::asio::ip::address::from_string(server.c_str ()), port);
    socket.connect(endpoint, error); */
  
  // Form the request. We specify the "Connection: close" header so that the
  // server will close the socket after transmitting the response. This will
  // allow us to treat all data up until the EOF as the content.
  
  // Send the request.
  boost::asio::write(socket, request);
    
  // Read the response status line.
  boost::asio::streambuf response;
  boost::asio::read_until(socket, response, "\r\n");
    
  // Check that response is OK.
  std::istream response_stream(&response);
  std::string http_version;
  response_stream >> http_version;
  unsigned int status_code;
  response_stream >> status_code;
  std::string status_message;
  std::getline(response_stream, status_message);
  if (!response_stream || http_version.substr(0, 5) != "HTTP/")
    {
      std::cout << "@@@ Invalid response" << std::endl;
      throw connector_error () ; 
    }
  if (status_code != 200)
    {
      std::cout << "@@@ Response returned with status code " << status_code << std::endl;
      throw connector_error () ; 
    }
    
  // Read the response headers, which are terminated by a blank line.
  boost::asio::read_until(socket, response, "\r\n\r\n");
    
  // Process the response headers.
  std::string header;
  while (std::getline(response_stream, header) && header != "\r") {}; 
    
  // Write whatever content we already have to output.
  std::ostringstream lanswer ; 
  if (response.size() > 0)
    lanswer << &response;
    
  // Read until EOF, writing data to output as we go.
  while (boost::asio::read(socket, response,
			   boost::asio::transfer_at_least(1), error))
    lanswer << &response;
  if (error != boost::asio::error::eof)
    throw boost::system::system_error(error);
  
  return lanswer.str (); 
}; 

std::string sign (std::string &request, std::string &login, std::string &password)
{
  std::ostringstream inputs ; 
  inputs << request ; 
  inputs << login ; 
  inputs << password ;
  std::string input = inputs.str (); 
  SHA256_CTX	ctx256;
  unsigned char	buf[16384];
  SHA256_Init(&ctx256);
  SHA256_Update(&ctx256, (unsigned char*)(input.c_str ()), (input.length ()));
  SHA256_End(&ctx256, (char*)buf);

  return (std::string ((const char *)buf)); 
 }; 

std::string new_job (std::string &server, std::string &login, std::string &password) 
{
  
  std::cout << "@@@ Connector: new_job\n"; 
  
  time_t seconds;
  seconds = time (NULL);

  std::string login_e = escape (login); 
  std::ostringstream tosign_s; 

  tosign_s << "new_job"; 
  tosign_s << "application="; 
  tosign_s << "corefarm"; 
  tosign_s << "date=" ; 
  tosign_s << seconds ;
  tosign_s << "isconfidential=" ; 
  tosign_s << "false" ;
  
  std::string tosign = tosign_s.str (); 
  std::string signature = sign (tosign, login, password); 
  std::string signature_e = escape (signature) ;
 
  std::cout << "@@@ Signature: " << signature_e << std::endl; 
  boost::asio::streambuf request;
  std::ostream request_stream(&request);
  
  request_stream << "GET /new_job?"; 
  request_stream << "application=corefarm" ;  
  request_stream << "&date=" << seconds ; 
  request_stream << "&isconfidential=false"; 
  request_stream << "&login=" << login_e ;
  request_stream << "&signature=" << signature_e ; 
  request_stream << " HTTP/1.0\r\n";
  
  request_stream << "Host: " << server << "\r\n";
  request_stream << "Accept: */*\r\n";
  request_stream << "User-Agent: Cfu\r\n"; 
  request_stream << "Connection: close\r\n\r\n";
    
  std::cout << "@@@ About to perform what has to be performed\n" << std::endl ; 

  std::string result = perform (server, request) ; 
  
  std::cout << "@@@ Result: " << result << std::endl; 

  int status = strip < int > (result, "status") ;
  if (!status) 
    {
      std::string msg = strip < std::string > (result, "msg");
      throw (corefarm_error (msg)); 
    }

  std::string id = strip < std::string > (result, "id");
  
  return id; 
}; 


void drop_compressed_file (std::string &server, std::string &login, std::string &password, std::string &job_id, std::string &filename)
{
  // On the fly compression
  
  std::string login_e = escape (login); 
  time_t seconds;
  seconds = time (NULL);
  
  std::ostringstream tosign_s; 

  tosign_s << "drop_file"; 
  tosign_s << "date="; 
  tosign_s << seconds ;
  tosign_s << "job_id=" ; 
  tosign_s << job_id ;

  std::string tosign = tosign_s.str (); 
  std::string signature = sign (tosign, login, password); 
  std::string signature_e = escape (signature) ;


  boost::filesystem::path f = boost::filesystem::path (filename) ; 
  
  std::string basename = f.filename () ; // This is the targetted name 
  
  std::string boundary ("---------------------------59719205020929667191569756715"); 
  boost::asio::streambuf request;
  std::ostream request_stream(&request);
  
      // First we build the header
  
    request_stream << "POST /drop_file?job_id=" << job_id ; 
  request_stream << "&date=" << seconds ; 
  request_stream << "&login=" << login_e; 
  request_stream << "&signature=" << signature_e; 
  request_stream << " HTTP/1.0\r\n";
  
  request_stream << "Host: " << server << "\r\n";
  request_stream << "Accept: */*\r\n";
  request_stream << "Keep-alive: 300\r\n"; 
  request_stream << "User-Agent: Prone (0.3.x)\r\n"; 
  
  request_stream << "Connection: keep-alive\r\n"; // ?!
  
  request_stream << "Content-Type: multipart/form-data; boundary=" << boundary << "\r\n";
  
  
  // Second we compute the size of the contents 
  std::ostringstream overhead;
  
  overhead <<  "--" << boundary << "\r\n";
  overhead << "Content-disposition: form-data; name=\"file\"; filename=\"" << basename << ".gz\"\r\n" ;
  overhead << "Content-Type: application/octet-stream\r\n\r\n";  
  overhead <<  "\r\n--" << boundary << "--\r\n";
  
  // And we have to compress the file to estimate it's size // This is silly, I know .. 
  
  int filesize = 0 ; 
  
  std::cout << "@@@ file to send: " << filename.c_str () << std::endl; 
  std::ifstream filein_size(filename.c_str (), std::ios_base::in | std::ios_base::binary);
  
  boost::iostreams::filtering_streambuf<boost::iostreams::input> strbuf_size;
  strbuf_size.push(boost::iostreams::gzip_compressor());
  strbuf_size.push(filein_size);
  
  std::ostringstream oss; 
  boost::iostreams::copy(strbuf_size, oss);
  
  std::string contents = oss.str () ; 
  filesize = contents.length () ; 
  
  int length =  (overhead.str ()).length () + filesize ;
    
  request_stream << "Content-length: " << length << "\r\n\r\n";
  request_stream <<  "--" << boundary << "\r\n";
  request_stream << "Content-disposition: form-data; name=\"file\"; filename=\"" << basename << ".gz\"\r\n" ;
  request_stream << "Content-Type: application/octet-stream\r\n\r\n"; 
    
  request_stream << contents;    
  request_stream << "\r\n--" << boundary << "--\r\n"; 
  
  std::string result = perform (server, request) ; 
  std::cout << "@@@ Server response: " << result << std::endl; 

  return ; 
}



void drop_file (std::string &server, std::string &login, std::string &password, std::string &job_id, std::string &filename)
{
  std::string login_e = escape (login); 
  time_t seconds;
  seconds = time (NULL);
    
  std::ostringstream tosign_s; 

  tosign_s << "drop_file"; 
  tosign_s << "date="; 
  tosign_s << seconds ;
  tosign_s << "job_id=" ; 
  tosign_s << job_id ;

  std::string tosign = tosign_s.str (); 
  std::string signature = sign (tosign, login, password); 
  std::string signature_e = escape (signature) ;

  boost::filesystem::path f = boost::filesystem::path (filename) ; 
  
  std::string basename = f.filename () ; // This is the targetted name 
  
  std::string boundary ("---------------------------59719205020929667191569756715"); 
  boost::asio::streambuf request;
  std::ostream request_stream(&request);
  
  request_stream << "POST /drop_file?job_id=" << job_id ; 
  request_stream << "&date=" << seconds ; 
  request_stream << "&login=" << login_e; 
  request_stream << "&signature=" << signature_e; 
  request_stream << " HTTP/1.0\r\n";
  
  request_stream << "Host: " << server << "\r\n";
  request_stream << "Accept: */*\r\n";
  request_stream << "Keep-alive: 300\r\n"; 
  request_stream << "User-Agent: Prone (0.3.x)\r\n"; 
  
  request_stream << "Connection: keep-alive\r\n"; // ?!
  
  request_stream << "Content-Type: multipart/form-data; boundary=" << boundary << "\r\n";
  
  std::ostringstream overhead;
  
  overhead <<  "--" << boundary << "\r\n";
  overhead << "Content-disposition: form-data; name=\"file\"; filename=\"" << basename << "\"\r\n" ;
  overhead << "Content-Type: application/octet-stream\r\n\r\n"; 
       
  overhead <<  "\r\n--" << boundary << "--\r\n";
  
  
  int length = boost::filesystem::file_size(f) + (overhead.str ()).length () ; 
  
  request_stream << "Content-length: " << length << "\r\n\r\n";
  request_stream <<  "--" << boundary << "\r\n";
  request_stream << "Content-disposition: form-data; name=\"file\"; filename=\"" << basename << "\"\r\n" ;
  request_stream << "Content-Type: application/octet-stream\r\n\r\n"; 
  
  std::ifstream ifs(filename.c_str (), std::ios::binary);
  
  request_stream << ifs.rdbuf ();     
  request_stream << "\r\n--" << boundary << "--\r\n"; 
  
  std::string result = perform (server, request) ; 
  std::cout << "@@@ Server response: " << result << std::endl; 
  
  return ;
}


void start_job (std::string &server, 
		std::string &login, 
		std::string &password, 
		std::string &job_id, 
		int ghz, 
		std::string &input)
{
  
  std::cout << "@@@ Connector: start_job\n"; 
  
  time_t seconds;
  seconds = time (NULL);

  std::string login_e = escape (login); 
  std::string input_e = escape (input); 
  std::ostringstream tosign_s; 

  tosign_s << "start_job"; 
  tosign_s << "date=" << seconds ;
  tosign_s << "ghz=" << ghz; 
  tosign_s << "input=" << input; 
  tosign_s << "job_id=" << job_id ; 

  std::string tosign = tosign_s.str (); 
  std::string signature = sign (tosign, login, password); 
  std::string signature_e = escape (signature) ;
 
  std::cout << "@@@ Signature: " << signature_e << std::endl; 
  boost::asio::streambuf request;
  std::ostream request_stream(&request);
  
  request_stream << "GET /start_job?"; 
  request_stream << "job_id=" << job_id ;  
  request_stream << "&date=" << seconds ; 
  request_stream << "&ghz=" << ghz ; 
  request_stream << "&login=" << login_e ; 
  request_stream << "&input=" << input_e ;   
  
  request_stream << "&signature=" << signature_e ; 
  request_stream << " HTTP/1.0\r\n";

  request_stream << "Host: " << server << "\r\n";
  request_stream << "Accept: */*\r\n";
  request_stream << "User-Agent: Cfu\r\n"; 
  request_stream << "Connection: close\r\n\r\n";
    
  std::string result = perform (server, request) ; 
  
  std::cout << "@@@ Result: " << result << std::endl; 

  int status = strip < int > (result, "status") ;
  if (!status) 
    {
      std::string msg = strip < std::string > (result, "msg");
      throw (corefarm_error (msg)); 
    }

  return; 
}; 
